Micron Document
C.S.Burner 🪙 ━►🔥GIT Node

Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / settings / NotificationSoundSettings.vue

Displaying Raw • Download

meshchatx/src/frontend/components/settings/NotificationSoundSettings.vue 65ca9ddc823fcc2c8f8653c553b944b85db0875f (65ca9ddc) Text, 11.82 KB

T8b949e<!-- SPDX-License-Identifier: 0BSD AND MIT -->

Tff7b72<templateTff7b72>
Tff7b72<section Te6edf3v-show=Ta5d6ff"Ta5d6ffshowSectionTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsettings-section break-inside-avoidTa5d6ff"Tff7b72>
Tff7b72<header Te6edf3class=Ta5d6ff"Ta5d6ffsettings-section__headerTa5d6ff"Tff7b72>
Tff7b72<divTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffsettings-section__eyebrowTa5d6ff"Tff7b72>{{ Te6edf3$t("app.notifications") }}Tff7b72</div>
Tff7b72<h2Tff7b72>{{ Te6edf3$t("app.notification_sound_settings") }}Tff7b72</h2>
Tff7b72<pTff7b72>{{ Te6edf3$t("app.notification_sound_settings_description") }}Tff7b72</p>
Tff7b72</div>
Tff7b72</header>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffsettings-section__body space-y-4Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-2xl border border-gray-200 dark:border-zinc-800 bg-white/70 dark:bg-zinc-900/70 px-3 py-3Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-start justify-between gap-3Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex-1 space-y-1Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold text-gray-900 dark:text-whiteTa5d6ff"Tff7b72>
{{ Te6edf3$t("app.enable_notification_sound") }}
Tff7b72</div>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-gray-600 dark:text-gray-300 leading-relaxedTa5d6ff"Tff7b72>
{{ Te6edf3$t("app.enable_notification_sound_description") }}
Tff7b72</p>
Tff7b72</div>
Tff7b72<Toggle
Te6edf3id=Ta5d6ff"Ta5d6ffnotification-sound-enabledTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 mt-0.5Ta5d6ff"
Te6edf3:model-value=Ta5d6ff"Ta5d6ffconfig.notification_sound_enabledTa5d6ff"
@Te6edf3update:model-value=Ta5d6ff"Ta5d6ffonEnabledChangeTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72</div>

Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffconfig.notification_sound_enabledTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffspace-y-4Ta5d6ff"Tff7b72>
Tff7b72<divTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center justify-between mb-2Ta5d6ff"Tff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold text-gray-700 dark:text-zinc-300Ta5d6ff"Tff7b72>
{{ Te6edf3$t("app.notification_sound_volume") }}
Tff7b72</label>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-xs font-mono text-gray-400Ta5d6ff"Tff7b72>{{ config.notification_sound_volume }}%Tff7b72</span>
Tff7b72</div>
Tff7b72<input
Te6edf3:value=Ta5d6ff"Ta5d6ffconfig.notification_sound_volumeTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffrangeTa5d6ff"
Te6edf3min=Ta5d6ff"Ta5d6ff0Ta5d6ff"
Te6edf3max=Ta5d6ff"Ta5d6ff100Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffw-full h-1.5 bg-gray-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-blue-600Ta5d6ff"
@Te6edf3input=Ta5d6ff"Ta5d6ffonVolumeChangeTa5d6ff"
Tff7b72/>
Tff7b72</div>

Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffspace-y-2Ta5d6ff"Tff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold text-gray-700 dark:text-zinc-300Ta5d6ff"Tff7b72>
{{ Te6edf3$t("app.notification_sound_default") }}
Tff7b72</label>
Tff7b72<select
Te6edf3:value=Ta5d6ff"Ta5d6ffconfig.notification_sound_preferred_idTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffinput-field py-1.5! px-3! text-sm! rounded-xl! border-gray-200! dark:border-zinc-800! w-full max-w-mdTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6ffonPreferredChangeTa5d6ff"
Tff7b72>
Tff7b72<option Te6edf3:value=Ta5d6ff"Ta5d6ff0Ta5d6ff"Tff7b72>{{ Te6edf3$t("app.notification_sound_primary_default") }}Tff7b72</option>
Tff7b72<option Te6edf3v-for=Ta5d6ff"Ta5d6ffsound in soundsTa5d6ff" Te6edf3:key=Ta5d6ff"Ta5d6ffsound.idTa5d6ff" Te6edf3:value=Ta5d6ff"Ta5d6ffsound.idTa5d6ff"Tff7b72>
{{ sound.display_name }}
Tff7b72</option>
Tff7b72</select>
Tff7b72</div>

Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center justify-betweenTa5d6ff"Tff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold text-gray-700 dark:text-zinc-300Ta5d6ff"Tff7b72>
{{ Te6edf3$t("app.notification_sounds") }}
Tff7b72</label>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6fftext-xs font-bold text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffisUploadingTa5d6ff"
@Te6edf3click=Ta5d6ff"Te6edf3$refs.soundUpload.clickTa5d6ff()Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffplusTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
{{ isUploading ? Te6edf3$t("app.notification_sound_uploading") : Te6edf3$t("app.notification_sound_upload") }}
Tff7b72</button>
Tff7b72<input
Te6edf3ref=Ta5d6ff"Ta5d6ffsoundUploadTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fffileTa5d6ff"
Te6edf3accept=Ta5d6ff"Ta5d6ff.mp3,.ogg,.wav,.m4a,.flac,audio/*Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffhiddenTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6ffuploadSoundTa5d6ff"
Tff7b72/>
Tff7b72</div>

Tff7b72<p Te6edf3v-if=Ta5d6ff"Ta5d6ffsounds.length === 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-gray-500 dark:text-zinc-400Ta5d6ff"Tff7b72>
{{ Te6edf3$t("app.notification_sound_none_uploaded") }}
Tff7b72</p>

Tff7b72<div v-else Te6edf3class=Ta5d6ff"Ta5d6ffgrid gap-3Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ffsound in soundsTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffsound.idTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center justify-between gap-3 rounded-xl border border-gray-200 dark:border-zinc-800 px-3 py-2Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex-1Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold truncateTa5d6ff"Tff7b72>{{ sound.display_name }}Tff7b72</div>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffsound.is_primaryTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-blue-600 dark:text-blue-400Ta5d6ff"Tff7b72>
{{ Te6edf3$t("app.notification_sound_primary") }}
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center gap-2 shrink-0Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-lg p-1.5 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-800Ta5d6ff"
Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffapp.notification_sound_previewTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffpreviewSound(sound)Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon
Te6edf3:icon-name=Ta5d6ff"Ta5d6ffplayingSoundId === sound.id ? Ta5d6ff'Ta5d6ffstopTa5d6ff'Ta5d6ff : Ta5d6ff'Ta5d6ffplayTa5d6ff'Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff"
Tff7b72/>
Tff7b72</button>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ff!sound.is_primaryTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-lg px-2 py-1 text-xs font-semibold text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffsetPrimarySound(sound)Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("app.notification_sound_set_primary") }}
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-lg p-1.5 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20Ta5d6ff"
Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffapp.notification_sound_removeTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffdeleteSound(sound)Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffdeleteTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</section>
Tff7b72</template>

Tff7b72<scriptTff7b72>
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import Toggle from "../forms/Toggle.vue";
import ToastUtils from "../../js/ToastUtils";
import NotificationSoundUtils from "../../js/NotificationSoundUtils";

export default {
name: "NotificationSoundSettings",
components: {
MaterialDesignIcon,
Toggle,
},
props: {
config: {
type: Object,
required: true,
},
showSection: {
type: Boolean,
default: true,
},
updateConfig: {
type: Function,
required: true,
},
},
emits: ["sounds-changed"],
data() {
return {
sounds: [],
isUploading: false,
playingSoundId: null,
};
},
mounted() {
this.loadSounds();
},
methods: {
async loadSounds() {
try {
const response = await window.api.get("/api/v1/notification-sounds");
this.sounds = response.data ?? [];
} catch (error) {
console.error("Failed to load notification sounds:", error);
this.sounds = [];
}
},
onEnabledChange(value) {
this.updateConfig({ notification_sound_enabled: value }, "notification_sound_enabled");
},
onVolumeChange(event) {
const value = Number(event.target.value);
this.updateConfig({ notification_sound_volume: value }, "notification_sound_volume");
},
onPreferredChange(event) {
const value = Number(event.target.value);
this.updateConfig({ notification_sound_preferred_id: value }, "notification_sound_preferred_id");
},
async uploadSound(event) {
const file = event.target.files?.[0];
if (!file) {
return;
}

this.isUploading = true;
const formData = new FormData();
formData.append("file", file);

try {
await window.api.post("/api/v1/notification-sounds/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
ToastUtils.success(this.$t("app.notification_sound_uploaded"));
await this.loadSounds();
this.$emit("sounds-changed");
} catch (error) {
console.error(error);
ToastUtils.error(error.response?.data?.message || this.$t("app.notification_sound_upload_failed"));
} finally {
this.isUploading = false;
event.target.value = "";
}
},
async deleteSound(sound) {
if (!confirm(this.$t("common.delete_confirm"))) {
return;
}
try {
await window.api.delete(`/api/v1/notification-sounds/${sound.id}`);
ToastUtils.success(this.$t("app.notification_sound_deleted"));
if (this.playingSoundId === sound.id) {
NotificationSoundUtils.stop();
this.playingSoundId = null;
}
await this.loadSounds();
this.$emit("sounds-changed");
} catch (error) {
console.error(error);
ToastUtils.error(this.$t("app.notification_sound_delete_failed"));
}
},
async setPrimarySound(sound) {
try {
await window.api.patch(`/api/v1/notification-sounds/${sound.id}`, {
is_primary: true,
});
ToastUtils.success(this.$t("app.notification_sound_primary_set"));
await this.loadSounds();
} catch (error) {
console.error(error);
ToastUtils.error(this.$t("app.notification_sound_primary_set_failed"));
}
},
async previewSound(sound) {
if (this.playingSoundId === sound.id) {
NotificationSoundUtils.stop();
this.playingSoundId = null;
return;
}

const played = await NotificationSoundUtils.preview(sound.id, this.config.notification_sound_volume ?? 100);
if (played) {
this.playingSoundId = sound.id;
return;
}
ToastUtils.warning(this.$t("app.notification_sound_preview_failed"));
},
},
};
Tff7b72</script>

Served by rngit 1.5.2 - Generated in 0.03s